package com.franklin.java.utils;

import com.flychord.jdk.util.matchcase.TupleCase;
import com.flychord.jdk.util.tuple.Tuple2;
import com.flychord.jdk.util.tuple.Tuple3;
import com.flychord.jdk.util.tuple.TupleAble;
import com.franklin.java.api.psi.data.PsiCallingElement;
import com.franklin.plugins.common.collection.PrettyArrayList;
import com.franklin.java.api.psi.data.PsiData;
import com.franklin.java.api.spring.annotation.bean.BeanAnnotation;
import com.franklin.java.api.spring.annotation.bean.BeanAnnotations;
import com.franklin.java.api.spring.annotation.bean.DependencyInjectionAnnotation;
import com.franklin.java.api.spring.annotation.bean.DependencyInjectionAnnotations;
import com.franklin.plugins.common.utils.DataCheckUtils;
import com.franklin.plugins.common.utils.StringHelper;
import com.intellij.codeInsight.completion.CompletionParameters;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.impl.java.stubs.index.JavaShortClassNameIndex;
import com.intellij.psi.impl.source.tree.PsiCommentImpl;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.PsiSearchHelper;
import com.intellij.psi.search.PsiShortNamesCache;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlElement;
import com.intellij.psi.xml.XmlTag;
import com.intellij.psi.xml.XmlText;
import com.intellij.util.indexing.IdFilter;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;
import java.util.stream.Collectors;

import static com.flychord.jdk.util.tuple.TupleAble.tuple;

/**
 * @author Franklin
 * @since 2021/8/5 11:00
 */
public class PsiUtils {

    public static PsiClass findClass(Project project,String path,String typeQualifiedName){
        String[] names = typeQualifiedName.split("\\.");
        PsiClass[] psiClasses = findClasses(project,names[names.length - 1]);
        if (ArrayUtils.isEmpty(psiClasses)){
            return null;
        }
        List<PsiClass> candidateClasses = new PrettyArrayList<>();
        for (PsiClass psiClass : psiClasses) {
            if (StringHelper.equals(psiClass.getQualifiedName(),typeQualifiedName)){
                candidateClasses.add(psiClass);
            }
        }
        return findClassNearby(typeQualifiedName, path, candidateClasses);
    }

    /**
     * 查找类里面的方法<br>
     * @author Franklin
     * @since 2021/9/7 10:15
     * @param project:
     * @param path:
     * @param className:
     * @param methodName:
     * @return java.util.List<com.intellij.psi.PsiMethod>
     */
    public static List<PsiMethod> findMethods(Project project,String path,String className,String methodName){
        PsiClass psiClass = findClass(project, path, className);
        if (Objects.isNull(psiClass)){
            return Collections.emptyList();
        }
        PsiMethod[] psiMethods = psiClass.getAllMethods();
        return Arrays.stream(psiMethods)
            .filter(psiMethod -> psiMethod.getName().equals(methodName))
            .collect(Collectors.toList());
    }

    public static PsiClass findClassByQualifiedOrSimpleName(Project project,String path,String typeQualifiedOrSimpleName){
        if (typeQualifiedOrSimpleName.contains(".")){
            return findClass(project, path, typeQualifiedOrSimpleName);
        }
        return findClassBySimpleName(project, path, typeQualifiedOrSimpleName);
    }

    public static PsiClass findClassBySimpleName(Project project,String path,String typeSimpleName){
        typeSimpleName = StringHelper.firstToUpperCase(typeSimpleName);
        PsiClass[] psiClasses = findClasses(project,typeSimpleName);
        if (ArrayUtils.isEmpty(psiClasses)){
            return null;
        }
        List<PsiClass> candidateClasses = new PrettyArrayList<>();
        Collections.addAll(candidateClasses,psiClasses);
        return findClassNearby(typeSimpleName, path, candidateClasses);
    }

    public static PsiClass[] findClasses(Project project,String typeName){
        PsiShortNamesCache psiShortNamesCache = PsiShortNamesCache.getInstance(project);
        GlobalSearchScope globalSearchScope = GlobalSearchScope.allScope(project);
        return psiShortNamesCache.getClassesByName(typeName,globalSearchScope);
    }


    public static PsiFile[] findFiles(Project project,String fileName){
        PsiShortNamesCache psiShortNamesCache = PsiShortNamesCache.getInstance(project);
        return psiShortNamesCache.getFilesByName(fileName);
    }

    public static PsiClass findAbstractMethodImplementClass(PsiData targetData, String callMethodName) {
        Project project = targetData.getPsiElement().getProject();
        PsiShortNamesCache psiShortNamesCache = PsiShortNamesCache.getInstance(project);
        GlobalSearchScope globalSearchScope = GlobalSearchScope.allScope(project);
        return getPsiClass(targetData,callMethodName,psiShortNamesCache,globalSearchScope);
    }

    @Nullable
    static PsiClass getPsiClass(
            PsiData targetData,
            String callMethodName,
            PsiShortNamesCache psiShortNamesCache,
            GlobalSearchScope globalSearchScope
    ) {
        PsiClass targetClass = null;
        PsiClass[] psiClasses = psiShortNamesCache.getClassesByName(targetData.getType(), globalSearchScope);
        List<PsiClass> psiClassList = new PrettyArrayList<>();
        //先尝试找java文件
        if (DataCheckUtils.isEmpty(targetData.getValue())) {

            //寻找真正的父类/接口
            PsiClass parentPsiClass = null;
            for (PsiClass psiClass : psiClasses) {
                if (StringHelper.equals(psiClass.getQualifiedName(), targetData.getTypeFullName())) {
                    parentPsiClass = psiClass;
                    break;
                }
            }

            if (Objects.isNull(parentPsiClass)) {
                return null;
            }

            //没有实例声明
            PsiMethod[] psiMethods = psiShortNamesCache.getMethodsByName(callMethodName, globalSearchScope);

            //寻找可能合适的实现类名字，并用Set去重
            Set<String> candidateClassNames = new HashSet<>();
            for (PsiMethod psiMethod : psiMethods) {
                PsiClass psiClass = psiMethod.getContainingClass();
                if (Objects.nonNull(psiClass)) {
                    //实现类
                    PsiClassType[] implementsListTypes = psiClass.getImplementsListTypes();
                    for (PsiClassType implementsListType : implementsListTypes) {
                        String className = implementsListType.getClassName();
                        if (StringHelper.equals(className, parentPsiClass.getName())) {
                            candidateClassNames.add(psiClass.getName());
                        }
                    }

                    //子类
                    PsiClassType[] extendsListTypes = psiClass.getExtendsListTypes();
                    for (PsiClassType implementsListType : extendsListTypes) {
                        String className = implementsListType.getClassName();
                        if (StringHelper.equals(className, parentPsiClass.getName())) {
                            candidateClassNames.add(psiClass.getName());
                        }
                    }
                }
            }

            //放入列表
            for (String candidateClassName : candidateClassNames) {
                PsiClass[] candidateClasses = psiShortNamesCache.getClassesByName(candidateClassName, globalSearchScope);
                Collections.addAll(psiClassList, candidateClasses);
            }
        } else {
            //有实例声明
            Collections.addAll(psiClassList, psiClasses);
        }
        //通过全类名匹配
        for (PsiClass psiClass : psiClassList) {
            String qualifiedName = psiClass.getQualifiedName();
            if (StringHelper.equals(qualifiedName, targetData.getTypeFullName())) {
                targetClass = psiClass;
                break;
            }
        }
        if (Objects.isNull(targetClass)) {
            List<PsiClass> candidateClasses = psiClassList;
            //通过Spring找
            PsiClass classBySpring = PsiUtils.findClassBySpring(targetData, candidateClasses);
            if (Objects.nonNull(classBySpring)) {
                return classBySpring;
            }

            //通过路径相似寻找
            targetClass = PsiUtils.findClassNearby(
                    targetData.getTypeFullName(),
                    targetData.getPsiElement().getContainingFile().getVirtualFile().getPath(),
                    candidateClasses
            );
        }
        return targetClass;
    }

    static PsiClass findClassBySpring(PsiData targetData, List<PsiClass> candidateClasses) {
        List<Tuple2<PsiClass, BeanAnnotation>> springClasses = new PrettyArrayList<>();
        for (PsiClass candidateClass : candidateClasses) {
            PsiModifierList modifierList = candidateClass.getModifierList();
            if (Objects.nonNull(modifierList)) {
                PsiAnnotation[] psiAnnotations = modifierList.getAnnotations();
                for (PsiAnnotation psiAnnotation : psiAnnotations) {
                    BeanAnnotation beanAnnotation = BeanAnnotations.parse(psiAnnotation);
                    if (Objects.nonNull(beanAnnotation)) {
                        springClasses.add(TupleAble.tuple(candidateClass, beanAnnotation));
                    }
                }
            }
        }

        if (DataCheckUtils.isEmpty(springClasses)) {
            return null;
        }

        PsiElement psiElement = targetData.getPsiElement();
        PsiAnnotation[] annotations = null;
        PsiType psiType = null;
        if (psiElement instanceof PsiField) {
            //类成员里的bean
            PsiField psiField = (PsiField) psiElement;
            PsiModifierList modifierList = psiField.getModifierList();
            if (Objects.nonNull(modifierList)) {
                annotations = modifierList.getAnnotations();
            }
            psiType = psiField.getType();
        } else if (psiElement instanceof PsiParameter) {
            //通过方法参数注入的Bean
            PsiParameter psiParameter = (PsiParameter) psiElement;
            PsiModifierList modifierList = psiParameter.getModifierList();
            if (Objects.nonNull(modifierList)) {
                annotations = modifierList.getAnnotations();
            }
            psiType = psiParameter.getType();
        }
        DependencyInjectionAnnotation diAnn = null;

        if (Objects.isNull(annotations)) {
            return null;
        }

        diAnn = DependencyInjectionAnnotations.parse(annotations, psiType);
        if (Objects.nonNull(diAnn)) {
            DependencyInjectionAnnotation finalDiAnn = diAnn;
            PsiClass targetClass = springClasses.stream()
                .filter(
                    TupleCase.matchP(
                        (psiClass, beanAnnotation) -> beanAnnotation.match(finalDiAnn.getInjectBeanName())
                    )
                )
                .map(Tuple2::_1)
                .findFirst()
                .orElse(null);
            return targetClass;
        }

        return null;
    }

    static PsiClass findClassNearby(String psiClassQualifiedName, String path, List<PsiClass> candidateClasses) {
        if (DataCheckUtils.isEmpty(candidateClasses)) {
            return null;
        }

        PsiClass targetClass;
        //全类名匹配失败用缩写匹配，同模块-》同包优先原则
        List<Tuple3<Integer, Integer, PsiClass>> sortedClasses = candidateClasses.stream()
            .map(psiClass -> {
                String samePackage = StringHelper.longestCommonSubString(psiClassQualifiedName, psiClass.getQualifiedName());
                PsiFile containingFile = psiClass.getContainingFile();
                String filePath = containingFile.getVirtualFile().getPath();
                String samePath = StringHelper.longestCommonSubString(path, filePath);
                return TupleAble.tuple(samePackage.length(), samePath.length(), psiClass);
            })
            .sorted((o1, o2) -> {
                if (!o1._1().equals(o2._1())) {
                    return o1._1().compareTo(o2._1());
                }
                return o1._2().compareTo(o2._2());
            })
            .collect(Collectors.toList());
        targetClass = sortedClasses.get(sortedClasses.size() - 1)._3();
        return targetClass;
    }

    /**
     * 判断是否是"."<br>
     *
     * @param psiElement:
     * @return boolean
     * @author Franklin
     * @since 2021/8/6 11:56
     */
    public static boolean isJavaDot(PsiElement psiElement) {
        if (Objects.nonNull(psiElement)) {
            return psiElement.getText().equals(".");
        }
        return false;
    }

    /**
     * 判断是否是关键字 "new"<br>
     *
     * @param psiElement:
     * @return boolean
     * @author Franklin
     * @since 2021/8/6 11:56
     */
    public static boolean isKeyWord_New(PsiElement psiElement) {
        if (Objects.nonNull(psiElement) && psiElement instanceof PsiKeyword) {
            PsiKeyword psiKeyword = (PsiKeyword) psiElement;
            return psiKeyword.getText().equals("new");
        }
        return false;
    }

    public static boolean isNewDeclaration(String target) {
        if (DataCheckUtils.isNotEmpty(target)) {
            return target.contains("new ");
        }
        return false;
    }

    public static String getSimpleClassName(String className) {
        if (DataCheckUtils.isNotEmpty(className)) {
            if (className.contains(".")) {
                String[] split = className.split("\\.");
                return split[split.length - 1];
            }
            return className;
        }
        return "";
    }

    public static String getAnnotationAttributeValue(@NotNull PsiAnnotation psiAnnotation, @NotNull String attributeName) {
        PsiAnnotationMemberValue attributeValue = psiAnnotation.findDeclaredAttributeValue(attributeName);
        if (Objects.isNull(attributeValue)){
            return "";
        }
        if (attributeValue instanceof PsiLiteralExpression) {
            PsiLiteralExpression psiLiteralExpression = (PsiLiteralExpression) attributeValue;
            Object value = psiLiteralExpression.getValue();
            return String.valueOf(value);
        }else if (attributeValue instanceof PsiArrayInitializerMemberValue){
            PsiArrayInitializerMemberValue psiArrayInitializerMemberValue = (PsiArrayInitializerMemberValue) attributeValue;
            PsiAnnotationMemberValue[] initializers = psiArrayInitializerMemberValue.getInitializers();
            return Arrays.stream(initializers)
                .map(PsiAnnotationMemberValue::getText)
                .map(sql -> {
                    if (sql.length() >= 2){
                        return sql.substring(1,sql.length() - 1);
                    }
                    return "";
                })
                .collect(Collectors.joining("\n"));
        }
        return "";
    }

    public static boolean isClassElement(@NotNull PsiElement psiElement) {
        String text = psiElement.getText();
        if (DataCheckUtils.isEmpty(text)) {
            return false;
        }
        return Character.isUpperCase(text.charAt(0));
    }

    private static Set<String> mapTypes;

    static {
        mapTypes = new HashSet<>();
        mapTypes.add("Map<String,Object>");
        mapTypes.add("HashMap<String,Object>");
        mapTypes.add("LinkedHashMap<String,Object>");
        mapTypes.add("TreeMap<String,Object>");
    }

    public static String findTypeFullName(PsiJavaFile psiJavaFile, String typeName) {
        typeName = StringUtils.deleteWhitespace(typeName);
        if (mapTypes.contains(typeName)){
            return "java.util.Map";
        }
        int index = typeName.indexOf("<");
        if (index >= 0){
            typeName = typeName.substring(0,index);
        }

        PsiClass[] classes = findClasses(psiJavaFile.getProject(), typeName);
        PsiClass classNearby = findClassNearby(typeName, psiJavaFile.getContainingFile().getVirtualFile().getPath(), Arrays.asList(classes));
        if (Objects.nonNull(classNearby)){
            return classNearby.getQualifiedName();
        }

        PsiImportList importList = psiJavaFile.getImportList();
        if (Objects.nonNull(importList)) {
            PsiImportStatement[] importStatements = importList.getImportStatements();
            for (PsiImportStatement importStatement : importStatements) {
                String qualifiedName = importStatement.getQualifiedName();
                if (StringHelper.equals(qualifiedName,typeName)){
                    return qualifiedName;
                }
                if (DataCheckUtils.isNotEmpty(qualifiedName)) {
                    String[] names = qualifiedName.split("\\.");
                    if (StringHelper.equals(names[names.length - 1],typeName)){
                        return qualifiedName;
                    }
                }
            }
        }
        //没有导包，说明同包
        return psiJavaFile.getPackageName() + "." + typeName;
    }

    public static PsiMethodCallExpression findFullMethodCallExpression(
        PsiElement psiElement,
        String fullMethodCallString
    ) {
        if (Objects.isNull(psiElement) || DataCheckUtils.isEmpty(fullMethodCallString)) {
            return null;
        }
        PsiElement tempElement = psiElement;
        while (true) {
            PsiMethodCallExpression psiMethodCallExpression = PsiTreeUtil.getParentOfType(tempElement, PsiMethodCallExpression.class);
            if (Objects.isNull(psiMethodCallExpression)) {
                return null;
            }
            if (psiMethodCallExpression.getText().equals(fullMethodCallString)) {
                return psiMethodCallExpression;
            }
            tempElement = psiMethodCallExpression;
        }
    }

    public static PsiMethodCallExpression findFullMethodCallExpression(PsiElement psiElement){
        if (Objects.isNull(psiElement)) {
            return null;
        }
        PsiElement tempElement = psiElement;
        while (true) {
            PsiMethodCallExpression psiMethodCallExpression = PsiTreeUtil.getParentOfType(tempElement, PsiMethodCallExpression.class);
            if (Objects.isNull(psiMethodCallExpression)) {
                break;
            }
            tempElement = psiMethodCallExpression;
        }
        if (tempElement instanceof PsiMethodCallExpression){
            return (PsiMethodCallExpression) tempElement;
        }
        return null;
    }

    public static int findStartOffsetInPsiFile(@Nullable PsiElement psiElement) {
        if (Objects.isNull(psiElement)){
            return -1;
        }
        return psiElement.getTextOffset();
//        int offset = 0;
//        PsiElement tempElement = psiElement;
//        while (!(tempElement.getParent() instanceof PsiFile)) {
//            offset += tempElement.getStartOffsetInParent();
//            tempElement = tempElement.getParent();
//        }
//        offset += tempElement.getStartOffsetInParent();
//        return offset;
    }

    private static Set<String> listMapType;

    static {
        listMapType = new HashSet<>();
        listMapType.add("List<Map<String,Object>>");
        listMapType.add("ArrayList<Map<String,Object>>");
        listMapType.add("Collection<Map<String,Object>>");
        listMapType.add("Set<Map<String,Object>>");
        listMapType.add("HashSet<Map<String,Object>>");
        listMapType.add("Page<Map<String,Object>>");
        listMapType.add("IPage<Map<String,Object>>");
        listMapType.add("Stream<Map<String,Object>>");
    }

    public static boolean isListMap(String typeName){
        if (DataCheckUtils.isEmpty(typeName)){
            return false;
        }
        typeName = StringUtils.deleteWhitespace(typeName);
        return listMapType.contains(typeName);
    }

    public static boolean isJavaMap(Project project,String typeQualifiedName){
        if (DataCheckUtils.isEmpty(typeQualifiedName)){
            return false;
        }
        int index = typeQualifiedName.indexOf("<");
        if (index >= 0){
            typeQualifiedName = typeQualifiedName.substring(0,index);
        }
        if (typeQualifiedName.equals("java.util.Map")){
            return true;
        }
        try {
            Class<?> aClass = Class.forName(typeQualifiedName);
            boolean isJdkMap = Map.class.isAssignableFrom(aClass);
            if (isJdkMap){
                return true;
            }
            //判断是不是自定义的子类
            PsiClass psiClass = findClass(project,project.getProjectFilePath(), typeQualifiedName);
            if (Objects.isNull(psiClass)){
                return false;
            }
            PsiClassType[] implementsListTypes = psiClass.getImplementsListTypes();
            for (PsiClassType implementsListType : implementsListTypes) {
                String className = implementsListType.getClassName();
                try {
                    Class<?> aClass1 = Class.forName(className);
                    if (Map.class.isAssignableFrom(aClass1)){
                        return true;
                    }
                } catch (ClassNotFoundException e) {

                }
            }

            implementsListTypes = psiClass.getExtendsListTypes();

            for (PsiClassType implementsListType : implementsListTypes) {
                String className = implementsListType.getClassName();
                try {
                    Class<?> aClass1 = Class.forName(className);
                    if (Map.class.isAssignableFrom(aClass1)){
                        return true;
                    }
                } catch (ClassNotFoundException e) {

                }
            }
        } catch (ClassNotFoundException e) {

        }
        return false;
    }

    /**
     * 解析getClass方法返回的类型<br>
     * @author Franklin
     * @since 2021/9/3 17:05
     * @param getClassMethodResult:
     * @return java.lang.String
     */
    public static String resolveTypeNameFromGetClassMethodResult(String getClassMethodResult){
        if (DataCheckUtils.isEmpty(getClassMethodResult)){
            return "";
        }
        if (!getClassMethodResult.contains("extend")){
            return "";
        }
        return getClassMethodResult.substring(getClassMethodResult.indexOf("extends ") + "extends ".length(), getClassMethodResult.length() - 1);
    }

    public static boolean isWhiteSpace(PsiElement psiElement){
        return Objects.nonNull(psiElement) && (psiElement instanceof PsiWhiteSpace);
    }

    /**
     * 寻找左边第一个点左边的变量
     * @param parameters
     * @return
     */
    public static PsiCallingElement findCallingElement(CompletionParameters parameters){
        PsiFile psiFile = parameters.getOriginalFile();
        int offset = parameters.getOffset();
        return findCallingElement(psiFile,offset);
    }

    public static PsiCallingElement findCallingElement(PsiElement psiElement){
        PsiFile psiFile = psiElement.getContainingFile();
        int offset = PsiUtils.findStartOffsetInPsiFile(psiElement);
        return findCallingElement(psiFile,offset);
    }

    public static PsiCallingElement findCallingElement(PsiFile psiFile,int offset){
        PsiElement tempElement = null;
        //找点
        while (!isJavaDot((tempElement = psiFile.findElementAt(offset)))){
            offset--;
        }
        //找到点左边第一个psiElement
        offset--;
        while (isWhiteSpace((tempElement = psiFile.findElementAt(offset)))){
            offset--;
        }
        if (Objects.nonNull(tempElement)) {
            TextRange textRange = tempElement.getTextRange();
            int startOffset = textRange.getStartOffset();
            return new PsiCallingElement(tempElement,startOffset);
        }
        return null;
    }

    public static PsiElement findFirstNotWhitespaceElementFromLeft(CompletionParameters parameters){
        PsiFile psiFile = parameters.getOriginalFile();
        int offset = parameters.getOffset();
        PsiElement tempElement = null;
        while (Objects.nonNull(tempElement = psiFile.findElementAt(offset))){
            if (tempElement instanceof PsiWhiteSpace){
                offset--;
            }else if (tempElement.getText().equals("(")){
                offset--;
            }else if (tempElement.getText().equals(")")){
                offset--;
            }else {
                break;
            }
        }
        return tempElement;
    }

    /**
     * 搜索器<br>
     * @author Franklin
     * @since 2021/9/7 11:03
     * @param project:
     * @return com.intellij.psi.search.PsiSearchHelper
     */
    public static PsiSearchHelper getPsiSearchHelper(Project project){
        return project.getService(PsiSearchHelper.class);
    }

    /**
     * 解析泛型<br>
     * @author Franklin
     * @since 2021/9/9 17:30
     * @param simpleTypeName:
     * @return java.lang.String
     */
    public static String resolveGenericType(String simpleTypeName) {
        if (DataCheckUtils.isEmpty(simpleTypeName)){
            return "";
        }
        int leftIndex = simpleTypeName.indexOf('<');
        int rightIndex = simpleTypeName.lastIndexOf('>');
        if (leftIndex >= 0 && rightIndex >= 0){
            return simpleTypeName.substring(leftIndex + 1,rightIndex);
        }
        return "";
    }

    public static boolean isJavaCollection(PsiType psiType){
        if (Objects.isNull(psiType)){
            return false;
        }
        return isJavaCollection(psiType.getCanonicalText());
    }

    public static boolean isJavaCollection(String typeCanonicalText) {
        int index = typeCanonicalText.indexOf("<");
        if (index >= 0){
            typeCanonicalText = typeCanonicalText.substring(0,index);
        }
        try {
            Class<?> colClass = Class.forName(typeCanonicalText);
            if (Collection.class.isAssignableFrom(colClass)){
                return true;
            }
        } catch (ClassNotFoundException e) {

        }
        return false;
    }
}
